package com.gitee.itembucket.core.storage

import com.gitee.itembucket.ItemBucket
import com.gitee.itembucket.core.bucket.Bucket
import com.gitee.itembucket.core.bucket.ItemOperationLog
import org.bukkit.OfflinePlayer
import org.bukkit.inventory.ItemStack
import taboolib.common5.Coerce
import taboolib.common5.util.decodeBase64
import taboolib.common5.util.encodeBase64
import taboolib.module.database.ColumnTypeSQL
import taboolib.module.database.Table
import taboolib.module.database.getHost
import taboolib.platform.util.deserializeToItemStack
import taboolib.platform.util.serializeToByteArray
import java.sql.Timestamp
import java.util.*
import java.util.concurrent.CompletableFuture

class StorageSQL : Storage {

    val host = ItemBucket.config.getHost("database")

    val table = Table("item_bucket", host) {
        add { id() }
        add("user") { type(ColumnTypeSQL.VARCHAR, 36) }
        add("amount") { type(ColumnTypeSQL.INT) }
        add("data") { type(ColumnTypeSQL.LONGTEXT) }
    }

    val tableLog = Table(ItemBucket.logTable, host) {
        add { id() }
        add("user") { type(ColumnTypeSQL.VARCHAR, 36) }
        add("operator_user") { type(ColumnTypeSQL.VARCHAR, 36) }
        add("type") { type(ColumnTypeSQL.VARCHAR, 5) }
        add("item_id") { type(ColumnTypeSQL.INT) }
        add("amount") { type(ColumnTypeSQL.INT) }
        add("create_time") { type(ColumnTypeSQL.TIMESTAMP) }
    }

    val dataSource by lazy { host.createDataSource() }

    init {
        table.createTable(dataSource)
        tableLog.createTable(dataSource)
    }

    override fun getBucket(player: OfflinePlayer): Bucket {
        val bucket = Bucket()
        table.select(dataSource) {
            where { "user" eq player.uniqueId.toString() }
        }.forEach {
            bucket.items += Bucket.Item(getLong("id"), fromItem(getString("data")), getLong("amount"))
        }

        return bucket
    }

    fun fromItem(base64: String): ItemStack {
        return base64.decodeBase64().deserializeToItemStack(true)
    }


    fun toBase64(item: Bucket.Item): String {
        return toBase64(item.stack)
    }

    fun toBase64(item: ItemStack): String {
        return item.serializeToByteArray(true).encodeBase64()
    }

    override fun getItemLogs(player: OfflinePlayer): List<ItemOperationLog> {
        return tableLog.select(dataSource) {
            where { "user" eq player.uniqueId.toString() }
            rows("id","operator_user", "item_id", "type", "amount", "create_time")
        }.map {
            ItemOperationLog(
                getLong("id"),
                UUID.fromString(getString("operator_user")),
                player.uniqueId,
                getLong("item_id"),
                getLong("amount"),
                ItemOperationLog.Type.valueOf(getString("type")),
                getTimestamp("create_time").time
            )
        }
    }

    override fun addItemLog(log: ItemOperationLog) {
        tableLog.insert(dataSource, "user","operator_user", "type", "item_id", "amount", "create_time") {
            value(log.user.toString(),log.operator.toString(), log.type.toString(), log.itemId, log.amount, Timestamp(log.createTime))
        }
    }

    override fun createItem(player: OfflinePlayer, item: Bucket.Item): CompletableFuture<Void> {
        val future = CompletableFuture<Void>()
        table.insert(dataSource, "user", "data", "amount") {
            value(player.uniqueId.toString(), toBase64(item.stack), item.getAmount())
            onFinally {
                item.id = generatedKeys.run {
                    next()
                    Coerce.toLong(getObject(1))
                }
                item.changed = false
                future.complete(null)
            }
        }
        return future
    }

    override fun removeItem(player: OfflinePlayer, item: Bucket.Item) {
        if (item.id == -1L) return

        table.delete(dataSource) {
            where {
                "user" eq player.uniqueId.toString()
                "id" eq item.id
            }
        }
    }

    override fun updateItem(player: OfflinePlayer, item: Bucket.Item) {
        if (item.id == -1L) return
        table.update(dataSource) {
            where {
                "id" eq item.id
                "user" eq player.uniqueId.toString()
            }
            set("amount", item.getAmount())
            onFinally {
                item.changed = false
            }
        }
    }
}